/**
* @file	projekt.c
* @author	Jan Sliacky
*
* Load balancing model on unreliable processors
*
* May 2010
*
* Copyright (c) 2009 by Jan Sliacky
* All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include "tpl.h"
#include "work.h"
#include "logging.h"
#include "statistics.h"
#include "lbmodel.h"
#include "tasks.h"
#include "globals.h"

enum Messages
{
    MSG_OFF = 0,
    MSG_ON,
    MSG_NEED_WORK,
    MSG_TASKS,
    MSG_ALLTASKS,
    MSG_WANT_UNASSIGN,
    MSG_ANSWER,
    MSG_END
};

char const* message_names[] = {
                                  "MSG_OFF",
                                  "MSG_ON",
                                  "MSG_NEED_WORK",
                                  "MSG_TASKS",
                                  "MSG_ALLTASKS",
                                  "MSG_WANT_UNASSIGN",
                                  "MSG_ANSWER",
                                  "MSG_END"
                              };

enum ProcStatuses
{
    ST_OFF = 0,									/* proc is off */
    ST_ON,										/* proc just wake up */
    ST_WORKING,									/* proc is computing */
    ST_WAIT4WORK,								/* proc is waiting to work */
    ST_WAIT4UNASSIGN,							/* proc is waiting to unassigned work */
    ST_WAIT4END,								/* proc is waiting to approved global end */
    ST_END										/* total end of computing */
};

char const* status_names[] = {
                                 "ST_OFF",
                                 "ST_ON",
                                 "ST_WORKING",
                                 "ST_WAIT4WORK",
                                 "ST_WAIT4UNASSIGN",
                                 "ST_WAIT4END",
                                 "ST_END"
                             };

enum Answers
{
    ANS_UNKNOWN = 0,
    ANS_YES,
    ANS_NO
};

/***************************************************************************************
* Local variables
****************************************************************************************/
static int myRank;						/* own unique rank of processor					*/
static enum ProcStatuses procStatus;	/* actual status of the processor				*/
static pthread_t solve_thread;			/* solve thread 								*/
static pthread_mutex_t global_mutex;	/* mutex for manipulating with all variables	*/
static TASK actualTask;					/* actually solving task						*/
static int answerArray[MAXPROCCOUNT];	/* array of answers from other procs			*/
static int procIDArray[MAXPROCCOUNT];	/* IDs of all neighbours procs					*/
static int mutexLockUnlockCounter;		/* count of lock - count of unlock				*/
static int current_fall;				/* number of current fall						*/
static _Bool waitCondition;				/* condition for interrupt waiting				*/
static double nextFall;					/* time to the next fall 						*/
init_config *input_cfg;					/* input configuration parameters 				*/
stats_config *stats_cfg;				/* statistics configuration parameters 			*/

/****************************************************************************************
* Static functions
*****************************************************************************************/

static int initialize(int *argc, char **argv);
static int deinitialize(void);
static int matchAll(int sender, int tag, void *message);
static int packTasksToDo(void *msg, int amount);
static int unpackMessage(void *messg);
static int sendMessage(int sender, int tag);
static void changeStatus(enum ProcStatuses status);
static void *solveThread(void *arg);

extern init_config* parseInputOptions(init_config* conf, int argc, char **argv);
extern stats_config* parseStatsOptions(stats_config* conf, int argc, char **argv);
extern void usage(char* prog);
extern init_config* readInputFile(init_config* config, char* filename);
extern void makeInputFile(init_config* config);

extern double fabs(double x);
extern int usleep (__useconds_t __useconds);

/**
 * Load values from input file and initialize variables
 * @author Jan Sliacky
 * @param	argc	number of input parameters
 * @param	argv	input parameters
 * @return SUCC on success or FAIL on failure
 */
static int initialize(int *argc, char **argv) {
    int i, j;
    int ipom;
    TASK tsk;
    float max_task=0;
    _Bool isSolvable = false;

    input_cfg=(init_config*)malloc(sizeof(init_config));
    input_cfg=readInputFile(input_cfg, argv[2]);

    if (input_cfg->procFallProbs[myRank] < 0.0001) {
    	nextFall = 99999;
    } else {
    	nextFall = input_cfg->fallProbs[myRank][0];
    }
    current_fall = 0;

    /* simple check input parameters */
    for (i = 0; i < input_cfg->nTasks; i++) {
        if (input_cfg->tasksArray[i] > max_task) {
            max_task = input_cfg->tasksArray[i];
        }
    }
    for (j = 0; j < input_cfg->nProcs; j++) {
        if (input_cfg->procFallProbs[j] > 0.001) {
            for (i = 0; i < RANDOMFALLSCOUNT; i++) {
                if (input_cfg->fallProbs[j][i] > max_task) {
                    isSolvable = true;
                    break;
                }
            }
        } else {
            isSolvable = true;
            break;
        }
    }
    if (!isSolvable) {
        printf("Posted input parameters from file has no solution. Try to reduce the fall probability of processors or shorten the time complexity of tasks\n\n");
        exit(EXIT_FAILURE);
    }

    /* initialize tpl */
    if (myRank == LEADER) {
        printf("... checking MPS Server ...\n");
    }
    tpl_initialize(&input_cfg->nProcs, &myRank, argc, &argv);
    if (myRank == LEADER) {
        printf("OK\n");
    }

    /* initialize work */
    work_initialize();

    /* initialize tasks handling */
    CHCKRESULT(initTasks(), "Mutex \"mut\" initialization error");

    /* initialize mutex */
    MUTEX_INIT(&global_mutex);

    actualTask.ID = -1;

    /* load tasks */
    for (i = 0; i < input_cfg->nTasks; i++) {
        tsk.ID = getTasksCount();
        tsk.value = input_cfg->tasksArray[i];
        tsk.status = TASK_UNSOLVED;
        addTask(&tsk);
        STAT(addToExpectedTime(tsk.value));
        if (myRank == LEADER) {
            addToTaskToDo(&tsk);
        }
    }

    /* procs IDs array initialization */
    ipom = 0;
    for(i = 0; i < input_cfg->nProcs; i++) {
        answerArray[i] = ANS_UNKNOWN;
        if (i != myRank) {
            procIDArray[ipom] = i;
            ipom += 1;
        }
    }

    /* initial status of processor */
    if (myRank == LEADER) {
        changeStatus(ST_WORKING);
    } else {
        changeStatus(ST_WAIT4WORK);
    }

    /* thread initialization */
    INITTHREAD("solve", &solve_thread, solveThread);

    LOG_DBG(printf("initialization complete"));
    return(EXIT_SUCCESS);
}

/**
 * Deinitialize tpl, work and destroy all relevant variables
 * @author Jan Sliacky
 * @return SUCC on success or FAIL on failure
 */
static int deinitialize(void) {
    LOG_INFO(printf("starting deinitialization ..."));
    JOINTHREADS("solve", solve_thread);
    if (mutexLockUnlockCounter != 0) {
        LOG_ERR(printf("number of locks and unlocks of mutex is not same"));
        destroyTasks();
        work_deinitialize();
        tpl_deinitialize();
        exit(EXIT_FAILURE);
    }
    MUTEX_DESTROY(&global_mutex);
    destroyTasks();
    work_deinitialize();
    tpl_deinitialize();
    LOG_INFO(printf("deinitialization complete !!!"));
    free(input_cfg);
    return(EXIT_SUCCESS);
}

/**
 * Dump some variables for debugging purposes
 * @author Jan Sliacky
 * @param	code	exit code
 */
static void dumpVariablesAndExit(int code) {
    int i;
    LOG_ERR(printf("======================================= FATAL ERROR ======================================="));
    LOG_ERR(printf("status: %d, actual_solved: %d, rank: %d, tasks: %d, todo: %d, solved: %d, rec_solved: %d", procStatus, actualTask.ID, myRank, getTasksCount(), getToDoTaskCount(), getSolvedTasksCount(), getRecentlySolvedTasksCount()));
    for (i = 0; i < getTasksCount(); i++) {
        LOG_ERR(printf("taks status of task %d = %d", i, getTaskStatus(i)));
    }
    LOG_ERR(printf("==========================================================================================="));
    exit(code);
}

/**
 * Matching function
 * @author Jan Sliacky
 * @param	sender	processor that sent the message
 * @param	tag		tag of the message
 * @param	message	pointer to the message
 * @return	EXIT_FAILURE
 */
static int matchAll(int sender, int tag, void *message) {
    return(EXIT_FAILURE);
}

/**
 * Reset all processors answers to ANS_UNKNOWN
 * @author Jan Sliacky
 */
static void resetAnswerArray(void) {
    int i;

    for(i = 0; i < input_cfg->nProcs; i++) {
        answerArray[i] = ANS_UNKNOWN;
    }
}

/**
 * Are answers from all processors ANS_YES ?
 * @author Jan Sliacky
 * @return true if all answers is ANS_YES, false if not
 */
static _Bool areAllYES(void) {
    int i;

    _Bool bpom = true;
    for(i = 0; i < input_cfg->nProcs; i++) {
        if ((i != myRank) && (answerArray[i] != ANS_YES)) {
            bpom = false;
            break;
        }
    }
    return bpom;
}

/**
 * Was answer from all processors sent ?
 * @author Jan Sliacky
 * @return true if answers from all processors received, false if not
 */
static _Bool areAllAnswered(void) {
    int i;
    _Bool bpom = true;
    for(i = 0; i < input_cfg->nProcs; i++) {
        if ((i != myRank) && (answerArray[i] == ANS_UNKNOWN)) {
            bpom = false;
        }
    }
    return bpom;
}

/**
 * Method to wait [wait_time] seconds for condition
 * @author Jan Sliacky
 * @param	wait_time	time to wait for condition
 * @param	stress		true = busy computing, false = sleep instead computing
 */
static _Bool waitForCondition(float wait_time, _Bool activeWorkSimulation) {

    waitCondition = false;
    startTimer();
    MUTEX_UNLOCK(&global_mutex);
    while ((wait_time > 0) && (waitCondition == false)) {
        if (!activeWorkSimulation) {
            if (wait_time < FALLWAITSTEP) {
                usleep(wait_time*1000000);
                nextFall -= wait_time;
            } else {
                usleep(FALLWAITSTEP*1000000);
                nextFall -= FALLWAITSTEP;
            }
        } else {
            if (wait_time < FALLWAITSTEP) {
            	nextFall -= empty_work(wait_time);
            } else {
            	nextFall -= empty_work(FALLWAITSTEP);
            }
        }
        if (nextFall < 0) {
        	waitCondition = true;
        }
        wait_time -= FALLWAITSTEP;
    }
    MUTEX_LOCK(&global_mutex);
    LOG_DBG(printf("i was waiting for %.3f seconds", getTimer()));
    return(waitCondition);
}

/**
 * Change status of processor
 * @param	status	new status of processor
 */
static void changeStatus(enum ProcStatuses status) {
    LOG_DBG(printf("my status was %s and now changed to %s", status_names[procStatus], status_names[status]));
    procStatus = status;
    resetAnswerArray();
    switch( status ) {
    case ST_OFF:
        STAT(incNFalls());
        resetTasksVariables();
        if (input_cfg->forgetStatuses) {
            clearTasksStatuses();
        }
        break;
    case ST_ON:
        procStatus = ST_WAIT4WORK;
        CHCKRESULT(sendMessage(BC, MSG_ON), "request with message MSG_ON failed");
        break;
    case ST_WORKING:
        waitCondition = true;
        break;
    case ST_WAIT4WORK:
        clearAssignedTasks();
        CHCKRESULT(sendMessage(BC, MSG_NEED_WORK), "request to empty task failed");
        break;
    case ST_WAIT4UNASSIGN:
        clearAssignedTasks();
        CHCKRESULT(sendMessage(BC, MSG_WANT_UNASSIGN), "request to unassign work");
        break;
    case ST_WAIT4END:
        LOG_INFO(printf("!!! all tasks is SOLVED !!!"));
        STAT(setEndTotalTime());
        waitCondition = true;
        CHCKRESULT(sendMessage(BC, MSG_END), "announce end processor failed");
        break;
    case ST_END:
        break;
    default:
        LOG_ERR(printf("wrong status"));
        break;
    }
}

/**
 * Pack actual task to message
 * @author Jan Sliacky
 * @param	taskID		task ID
 * @param	taskStatus	task status
 * @param	msg			pointer to message
 */
static void packAssignTask(int taskID, void *msg) {
	int ipom;

    LOG_DBG(printf("packing task with ID = %d", taskID));
    tpl_pkint(msg, &taskID, 1);
    ipom = TASK_ASSIGN;
    tpl_pkint(msg, &ipom, 1);
}

/**
 * Pack actual task to message
 * @author Jan Sliacky
 * @param	msg		pointer to message
 */
static void packActualTask(void *msg) {
    int ipom;

    LOG_DBG(printf("packing actual task with ID = %d", actualTask.ID));
    tpl_pkint(msg, &actualTask.ID, 1);
    ipom = TASK_ASSIGN;
    tpl_pkint(msg, &ipom, 1);
}

/**
 * Pack to-do tasks to message
 * @author Jan Sliacky
 * @param	msg		pointer to message
 * @param	amount	amount of tasks to pack
 * @return EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
static int packTasksToDo(void *msg, int amount) {
    int i, ipom;
    struct Task t;

    LOG_DBG(printf("packing %d todo tasks from %d todo tasks", amount, getToDoTaskCount()));
    for(i = 0; i < amount; i++) {
        CHCKRESULT(takeFromTaskToDo(&t), "taking task from the todo container failed");
        tpl_pkint(msg, &t.ID, 1);
        ipom = t.status;
        tpl_pkint(msg, &ipom, 1);
        LOG_TRC(printf("pack task %d with status %d", t.ID, ipom));
    }
    return(EXIT_SUCCESS);
}

/**
 * Pack recently solved tasks to message
 * @author Jan Sliacky
 * @param	msg		pointer to message
 * @param	amount	mount of tasks to pack
 * @return EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
static int packRecentlySolvedTasks(void *msg, int amount) {
    int i, ipom;
    struct Task t;

    LOG_DBG(printf("packing %d recently solved tasks", amount));
    for(i = 0; i < amount; i++) {
        CHCKRESULT(takeFromSolvedTasks(&t), "taking task from the solved container failed");
        tpl_pkint(msg, &t.ID, 1);
        ipom = t.status;
        tpl_pkint(msg, &ipom, 1);
        LOG_TRC(printf("pack task %d with status %d", t.ID, ipom));
    }
    return(EXIT_SUCCESS);
}

/**
 * Pack all solved tasks to message
 * @author Jan Sliacky
 * @param	msg		pointer to message
 * @param	amount	amount of tasks to pack
 * @return EXIT_SUCCESS on success, EXIT_FAILURE on failure
 */
static int packAllSolvedTasks(void *msg, int amount) {
    int i, ipom;
    struct Task t;

    LOG_DBG(printf("pack %d solved tasks", amount));
    t.ID = -1;
    for (i = 0; i < amount; i++) {
        CHCKRESULT(getNextSolvedTask(&t, t.ID + 1), "no solved task left");
        tpl_pkint(msg, &t.ID, 1);
        ipom = t.status;
        tpl_pkint(msg, &ipom, 1);
        LOG_TRC(printf("pack solved task with ID %d with status %d", t.ID, ipom));
    }
    return(EXIT_SUCCESS);
}

/**
 * Unpack tasks from message
 * @author Jan Sliacky
 * @param	msg		pointer to message
 * @return count of new unsolved messages
 */
static int unpackMessage(void *msg) {
    int ntasks_old, i, amount, ipom, new;
    struct Task t;

    new = 0;
    tpl_upkint(msg, &amount, 1);
    LOG_DBG(printf("unpack %d tasks", amount));
    for(i = 0; i < amount; i++) {
        tpl_upkint(msg, &t.ID, 1);
        tpl_upkint(msg, &ipom, 1);
        LOG_TRC(printf("task ID = %d, task status = %d", t.ID, ipom));
        t.status = (enum TaskStatuses)ipom;
        if ((t.ID == actualTask.ID) && (procStatus == ST_WORKING) && (actualTask.status != TASK_SOLVED)) {
        	waitCondition = true;
        }
        ntasks_old = getToDoTaskCount();
        addToTaskToDo(&t);
        if (getToDoTaskCount() > ntasks_old) {
            new++;
        }
    }
    LOG_DBG(printf("count of new todo tasks = %d", new));
    return(new);
}

/**
 * Return amount of tasks according to input_cfg->amount
 * @author Jan Sliacky
 * @return amount of tasks
 */
static int getAmountOfTasks(void) {
	if (input_cfg->amount == one) {
		return(1);
	} else
		if (input_cfg->amount == npart) {
			return((getToDoTaskCount() + input_cfg->nProcs - 1) / input_cfg->nProcs);
		} else
			if (input_cfg->amount == all) {
				return(getToDoTaskCount());
			}
	return (-1);
}

/**
 * send message with appropriate task and tagchangeStatus
 * @author Jan Sliacky
 * @param	recepient	receiver of the message
 * @param	tag			tag of the message
 * @return SUCC on success, FAIL on failure
 */
static int sendMessage(int recepient, int tag) {
    void *message;
    struct timeval tv;
    int amount, amount2, answer;
    double theTime;
    struct Task task;

    tpl_create(&message);

    /* pack send time */
    if (input_cfg->latency > 0) {
        gettimeofday(&tv, NULL);
        theTime = tv.tv_sec + (0.000001f * tv.tv_usec);
        tpl_pkdouble(message, &theTime, 1);
    }

    switch (tag) {
    case MSG_OFF:
        break;
    case MSG_ON:
        amount = getSolvedTasksCount();
        tpl_pkint(message, &amount, 1);
        CHCKRESULT(packAllSolvedTasks(message, amount), "packing solved tasks failed");
        break;
    case MSG_TASKS:
        /* if processor has any unsolved tasks, it packs some */
        if (getToDoTaskCount() > 0) {
            amount = getAmountOfTasks();
            /* don't send all Todotasks, when solvethread isn't start yet */
            if ((actualTask.ID == -1) && (amount == getToDoTaskCount())) {
                amount--;
            }
            if (amount == 0) {
            	amount = getSolvedTasksCount() + 1;
            	tpl_pkint(message, &amount, 1);
            	takeFromTaskToDo(&task);
            	packAssignTask(task.ID, message);
            	addToTaskToDo(&task);
            	CHCKRESULT(packAllSolvedTasks(message, amount-1), "packing solved tasks failed");
            } else {
            	tpl_pkint(message, &amount, 1);
            	CHCKRESULT(packTasksToDo(message, amount), "packing tasks error");
            }
        }
        /* if not, it packs only solved tasks */
        else {
            amount = getSolvedTasksCount();
            if (procStatus == ST_WORKING) {
                amount2 = amount + 1;
                tpl_pkint(message, &amount2, 1);
                packActualTask(message);
            } else {
                tpl_pkint(message, &amount, 1);
            }
            CHCKRESULT(packAllSolvedTasks(message, amount), "packing solved tasks failed");
        }
        break;
    case MSG_ALLTASKS:
        /* if processor has any unsolved tasks, it packs some and all solved tasks, if not, it packs only solved tasks */
        if (getToDoTaskCount() == 0) {
            amount = 0;
        } else {
        	amount = getAmountOfTasks();
        }
        amount2 = amount + getSolvedTasksCount();
        if (procStatus == ST_WORKING) {
            amount2 = amount2 + 1;
            tpl_pkint(message, &amount2, 1);
            packActualTask(message);
        } else {
            tpl_pkint(message, &amount2, 1);
        }
        CHCKRESULT(packTasksToDo(message, amount), "packing tasks error");
        CHCKRESULT(packAllSolvedTasks(message, getSolvedTasksCount()), "packing solved tasks failed");
        break;
    case MSG_NEED_WORK:
        amount = getRecentlySolvedTasksCount();
        tpl_pkint(message, &amount, 1);
        CHCKRESULT(packRecentlySolvedTasks(message, amount), "taking task from the solved container failed");
        STAT(incNNeedWorkBC());
        break;
    case MSG_ANSWER:
        if ((procStatus == ST_WAIT4UNASSIGN) && (myRank > recepient)) {
            answer = ANS_NO;
            LOG_DBG(printf("my answer is NO"));
        } else {
            answer = ANS_YES;
            LOG_DBG(printf("my answer is YES"));
        }
        tpl_pkint(message, &answer, 1);
        break;
    case MSG_WANT_UNASSIGN:
        STAT(incNWantUnassignBC());
        break;
    case MSG_END:
        break;
    default:
        LOG_ERR(printf("unknown tag %d", tag));
        break;
    }

    /* send message to one recepient */
    if ((recepient > -1) && (recepient < input_cfg->nProcs)) {
        LOG_MSG(printf("message with tag %s to recepient %d send", message_names[tag], recepient));
        tpl_send(&recepient, 1, tag, message);
        STAT(incNSends());
    } else {
        /* send message as broadcast */
        if ((recepient == -1) && (input_cfg->nProcs > 1)) {
            LOG_MSG(printf("BC send with tag = %s", message_names[tag]));
            tpl_send(procIDArray, input_cfg->nProcs-1, tag, message);
            STAT(incNBC());
        } else {
            /* send message to myself, if i'm alone */
            if ((recepient == -1) && (input_cfg->nProcs == 1)) {
                LOG_MSG(printf("message with tag %s to myself send", message_names[tag]));
                tpl_send(&myRank, 1, tag, message);
                STAT(incNBC());
            } else {
                /* wrong or no recepient */
                LOG_ERR(printf("no or wrong recepient (recepient=%d)", recepient));
                return(EXIT_FAILURE);
            }
        }
    }
    return(EXIT_SUCCESS);
}

/**
 * Solve task
 * @author Jan Sliacky
 * @param	taskTime	tasks time complexity
 */
static void solveTask(void) {
    LOG_DBG(printf("solving task: %d:%.3f", actualTask.ID, actualTask.value));
    STAT(setStartSolveTasksTime());
    STAT(setStartWasteTime());
    /* calculation itself */
    if (waitForCondition(actualTask.value, (myRank < input_cfg->nHwCores)) ) {
    	STAT(setEndWasteTime());
    	if (nextFall < 0) {
    		return;
    	}
    	if ((getToDoTaskCount() == 0) && (procStatus < ST_WAIT4END)) {
    	    changeStatus(ST_WAIT4WORK);
    	}
    	return;
    }
    LOG_INFO(printf("task - %d:%.3f was solved", actualTask.ID, actualTask.value));
    STAT(setEndSolveTasksTime());
    STAT(addMySolvedTask(actualTask.ID, actualTask.value));
    actualTask.status = TASK_SOLVED;
    addToSolvedTasks(&actualTask);
    LOG_TRC(printf("actual_solved: %d, rank: %d, tasks: %d, todo: %d, solved: %d, rec_solved: %d",
                   actualTask.ID, myRank, getTasksCount(), getToDoTaskCount(), getSolvedTasksCount(), getRecentlySolvedTasksCount()));
    /* all tasks is solved */
    if (areAllSolved()) {
        changeStatus(ST_WAIT4END);
    }
    /* no work */
    else if (getToDoTaskCount() == 0) {
        changeStatus(ST_WAIT4WORK);
    }
}

/**
 * Thread to solve tasks from taskToDo queue
 * @author Jan Sliacky
 */
static void *solveThread(void *arg) {

    MUTEX_LOCK(&global_mutex);
    STAT(setStartTotalTime());
    /* is all tasks solved ? */
    while (procStatus < ST_WAIT4END) {
        /* is time to fall ? */
        if (nextFall < 0) {
            changeStatus(ST_OFF);
            LOG_DBG(printf("i'm going down for %.3f seconds", input_cfg->fallDur[myRank][current_fall]));
            STAT(setStartFallTime());
            nextFall = INT_MAX;
            waitForCondition(input_cfg->fallDur[myRank][current_fall], (myRank < input_cfg->nHwCores));
            STAT(setEndFallTime());
            /* if not end send MSG_ON */
            if (procStatus < ST_WAIT4END) {
                changeStatus(ST_ON);
            }
            current_fall = ((current_fall+1)%RANDOMFALLSCOUNT);
            nextFall = input_cfg->fallProbs[myRank][current_fall];
        }
        /* pick and solve task */
        if (takeFromTaskToDo(&actualTask) == EXIT_SUCCESS) {
            /* do I have enough time to solve task ? */
            solveTask();
        } else {
            /* wait for some work */
        	STAT(setStartWaitTime());
            waitForCondition(INT_MAX, (myRank < input_cfg->nHwCores));
            STAT(setEndWaitTime());
        }
    }
    MUTEX_UNLOCK(&global_mutex);
    LOG_INFO(printf("solve thread end"));
}

/**
 * Handle request for work
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 * @param	message		message
 */
static void handleMSG_NEED_WORK(int send_from, void *message) {
    unpackMessage(message);
    if (areAllSolved()) {
        changeStatus(ST_WAIT4END);
    } else {
        CHCKRESULT(sendMessage(send_from, MSG_TASKS), "sending MSG_TASKS failed");
    }
}

/**
 * Handle request for work and status of computing
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 * @param	message		message
 */
static void handleMSG_ON(int send_from, void *message) {
    unpackMessage(message);
    if (areAllSolved()) {
        changeStatus(ST_WAIT4END);
    } else {
        CHCKRESULT(sendMessage(send_from, MSG_ALLTASKS), "sending MSG_TASKS failed");
    }
}

/**
 * Handle answer to request to unassign tasks
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 * @param	message		message
 * @param	isOff		true if processor has status ST_OFF
 */
static void handleMSG_ANSWER(int send_from, void *message, _Bool isOff) {
    int ans;

    if (isOff == false) {
        tpl_upkint(message, &ans, 1);
    } else {
        ans = ANS_YES;
    }
    answerArray[send_from] = ans;
    LOG_DBG(printf("answer %d from sender %d received", ans, send_from));
    if (areAllYES()) {
        if (getUnassignTasks() > 0) {
            LOG_INFO(printf("i'm getting all %d unassigned tasks", getToDoTaskCount()));
            changeStatus(ST_WORKING);
        } else {
            if (getFirstAssignTask() == EXIT_SUCCESS) {
                LOG_INFO(printf("no unassign tasks, i'm getting assigned task"));
                changeStatus(ST_WORKING);
            } else {
                LOG_ERR(printf("!!! Something very nasty happend !!!"));
                dumpVariablesAndExit(2);
            }
        }
    } else {
        if (areAllAnswered()) {
            LOG_INFO(printf("i cannot get an unassigned tasks"));
            changeStatus(ST_WAIT4WORK);
        }
    }
}

/**
 * Handle message with tasks
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 * @param	message		message
 * @param	isOff		true if processor has status ST_OFF
 */
static void handleMSG_TASKS(int send_from, void *message, _Bool isOff) {
    if (isOff == false) {
        unpackMessage(message);
        if (areAllSolved()) {
            changeStatus(ST_WAIT4END);
        }
    }
    if (getToDoTaskCount() > 0) {
        if (procStatus != ST_WORKING) {
            changeStatus(ST_WORKING);
            LOG_DBG(printf("new unsolved tasks arrived"));
        }
    } else {
        answerArray[send_from] = ANS_YES;
        LOG_DBG(printf("processor %d sent no unsolved tasks", send_from));
        if (areAllYES()) {
            if (isUnassignTask()) {
                if (input_cfg->takeAll == true) {
                    handleMSG_ANSWER(send_from, message, true);
                } else {
                    changeStatus(ST_WAIT4UNASSIGN);
                }
            } else {
                if (getFirstAssignTask() == EXIT_SUCCESS) {
                    LOG_DBG(printf("no unassign tasks, i'm getting assigned task"));
                    changeStatus(ST_WORKING);
                } else {
                    LOG_ERR(printf("!!!!!!!! Something very nasty happend !!!!!!!"));
                    dumpVariablesAndExit(1);
                }
            }
        }
    }
}

/**
 * Handle message when someone want unassign tasks
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 * @param	message		message
 */
static void handleMSG_WANT_UNASSIGN(int send_from, void *message) {
    if (getToDoTaskCount() == 0) {
        CHCKRESULT(sendMessage(send_from, MSG_ANSWER), "sending MSG_ANSWER failed");
    } else {
        CHCKRESULT(sendMessage(send_from, MSG_TASKS), "sending MSG_TASKS failed");
    }
}

/**
 * Handle off message
 * @author Jan Sliacky
 * @param	tag			tag of the message
 * @param	send_from	sender of the message
 * @param	message		message
 */
static void handleMSG_OFF(int tag, int send_from, void *message) {
    if (procStatus == ST_OFF) {
        if (tag == MSG_ON || tag == MSG_NEED_WORK || tag == MSG_WANT_UNASSIGN) {
            STAT(incNOFFSends());
            sendMessage(send_from, MSG_OFF);
        }
    } else {
        if (procStatus == ST_WAIT4WORK) {
            handleMSG_TASKS(send_from, message, true);
        } else {
            if (procStatus == ST_WAIT4UNASSIGN) {
                handleMSG_ANSWER(send_from, message, true);
            }
        }
    }
}

/**
 * Handle end message
 * @param	send_from	sender of the message
 * @author Jan Sliacky
 */
static void handleMSG_END(int send_from) {
    if (procStatus != ST_WAIT4END) {
        changeStatus(ST_WAIT4END);
    }
    answerArray[send_from] = ANS_YES;
    if (areAllYES()) {
        changeStatus(ST_END);
    }
}

/**
 * Simulate latency of messages
 * @author Jan Sliacky
 * @param	send_from	sender of the message
 */
static void simulateLatency(void *message, int send_from) {
    struct timeval tv;
    double sendTime, actualTime;
    float latency;

    if (input_cfg->latency > 0) {
        tpl_upkdouble(message, &sendTime, 1);
        gettimeofday(&tv, NULL);
        actualTime = tv.tv_sec + (0.000001f * tv.tv_usec);
        latency = input_cfg->latency - (actualTime - sendTime);
        LOG_MSG(printf("latency of the message from processor %d is %.6lf seconds", send_from, latency));
        if (latency > 0) {
            usleep(latency*1000000);
        }
    }
}

/**
 * Method to receive and to handle messages
 * @author Jan Sliacky
 */
static void communicationThread(void) {
    int tag, send_from;
    void *message;

    MUTEX_LOCK(&global_mutex);
    /* if status of processor is not ST_END receives messages */
    while (procStatus != ST_END) {
        MUTEX_UNLOCK(&global_mutex);
        tpl_recv(matchAll, &send_from, &tag, &message);
        simulateLatency(message, send_from);
        MUTEX_LOCK(&global_mutex);
        LOG_MSG(printf("message with tag %s from sender %d received", message_names[tag], send_from));
        /* if something goes wrong exit */
        if (send_from < 0 || send_from > input_cfg->nProcs || tag < 0 || tag > 7 ) {
            LOG_ERR(printf("something goes terribly wrong !!!"));
            dumpVariablesAndExit(3);
        } else {
            if (tag == MSG_END) {
                handleMSG_END(send_from);
            }
            /* if status is ST_OFF or is received MSG_OFF */
            else {
                if (procStatus == ST_OFF || tag == MSG_OFF) {
                    handleMSG_OFF(tag, send_from, message);
                } else {
                    /* if processor isn't waiting for end*/
                    if (procStatus < ST_WAIT4END) {
                        switch( tag ) {
                        case MSG_ON:
                            handleMSG_ON(send_from, message);
                            break;
                        case MSG_NEED_WORK:
                            handleMSG_NEED_WORK(send_from, message);
                            break;
                        case MSG_TASKS:
                            handleMSG_TASKS(send_from, message, false);
                            break;
                        case MSG_ALLTASKS:
                            handleMSG_TASKS(send_from, message, false);
                            break;
                        case MSG_WANT_UNASSIGN:
                            handleMSG_WANT_UNASSIGN(send_from, message);
                            break;
                        case MSG_ANSWER:
                            handleMSG_ANSWER(send_from, message, false);
                            break;
                        default:
                            LOG_ERR(printf("Unexpected status received %d!!!", procStatus));
                            dumpVariablesAndExit(4);
                            break;
                        }
                    }
                }
            }
            tpl_destroy(message);
        }
    }
    MUTEX_UNLOCK(&global_mutex);
}

int main(int argc, char** argv) {
    int procs_count;

    if(argc == 1) {
        usage(argv[0]);
        exit(EXIT_SUCCESS);
    }
    if (strcmp(argv[1], "gen-input") == 0) {
        input_cfg=(init_config*)malloc(sizeof(init_config));
        input_cfg=parseInputOptions(input_cfg, argc, argv);
        makeInputFile(input_cfg);
        free(input_cfg);
    } else {
        if (strcmp(argv[1], "gen-stats") == 0) {
            stats_cfg=(stats_config*)malloc(sizeof(stats_config));
            stats_cfg=parseStatsOptions(stats_cfg, argc, argv);
            free(stats_cfg);
        } else {
            if (strcmp(argv[1], "run") == 0) {
                CHCKRESULT(initialize(&argc, argv), "Initialization failed");
                procs_count = input_cfg->nProcs;
                communicationThread();
                STAT(saveLocalStatistics(argv[2], myRank));
                deinitialize();
                if (myRank == 0) {
                    STAT(saveGlobalStatistics(argv[2], procs_count));
                }
            } else {
                usage(argv[0]);
                exit(EXIT_SUCCESS);
            }
        }
    }
    return(EXIT_SUCCESS);
}
